package com.gupao.javavip.spirng.framework.webmvc;

import com.gupao.javavip.spirng.framework.annotation.MyController;
import com.gupao.javavip.spirng.framework.annotation.MyRequestMapping;
import com.gupao.javavip.spirng.framework.annotation.MyRequestParam;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author: king
 * @Description:
 * @Date:Create：in 2019/11/3 22:05
 * @Modified By：
 */
@Slf4j
public class MyDispatcherServlet extends HttpServlet {
    private static final String CONTEXT_CONFIG_LOCATION = "configLocation";

    private List<Class> controllers = new ArrayList<>();

    private List<MyHandlerMapping> handlerMappings = new ArrayList<MyHandlerMapping>();


    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatch(req, resp);
    }

    @Override
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doDispatch(req, resp);
    }

    private void doDispatch(HttpServletRequest req, HttpServletResponse resp) {
        MyHandlerMapping handlerMapping = getMapping(req);

        try {
            if (handlerMapping == null) {
                String err = "404：" + req.getRequestURI();
                log.error(err);
                resp.getWriter().write(err);
                return;
            }


            Object[] args = buildHandlerMethodArgs(handlerMapping, req, resp);

            String result = (String) handlerMapping.getMethod().invoke(handlerMapping.getController(), args);
            resp.getWriter().write(result);
        } catch (Exception e) {
            log.error(e.getMessage(), e);
        }
    }

    private Object[] buildHandlerMethodArgs(MyHandlerMapping handlerMapping, HttpServletRequest req, HttpServletResponse resp) {
        Parameter[] params = handlerMapping.getMethod().getParameters();

        //提取方法中加了注解的参数
        //把方法上的注解拿到，得到的是一个二维数组
        //因为一个参数可以有多个注解，而一个方法又有多个参数
        Annotation[][] parameterAnnotations = handlerMapping.getMethod().getParameterAnnotations();

        Object[] args = new Object[params.length];
        for (int i = 0; i < params.length; i++) {
            Parameter param = params[i];
            Class type = param.getType();
            if (type == HttpServletRequest.class) {
                args[i] = req;
            } else if (type == HttpServletResponse.class) {
                args[i] = resp;
            } else {
                //默认通过参数名称到Request中获取相同名称的参数值
                String paramValue = req.getParameter(param.getName());

                //如果没有获取到，则看是否有 MyRequestParam注解值的参数值

                if (paramValue == null) {
                    String parameterAnnotationValue = getParameterAnnotationValue(parameterAnnotations[i]);
                    if (StringUtils.isNotBlank(parameterAnnotationValue)) {
                        paramValue = req.getParameter(parameterAnnotationValue);
                    }
                }
                args[i] = paramValue;
            }
        }
        return args;
    }

    private String getParameterAnnotationValue(Annotation[] parameterAnnotations) {
        for (Annotation annotation : parameterAnnotations) {
            if (annotation instanceof MyRequestParam) {
                return ((MyRequestParam) annotation).value();
            }
        }
        return null;
    }

    private MyHandlerMapping getMapping(HttpServletRequest req) {
        if (this.handlerMappings.isEmpty()) {
            return null;
        }

        String url = req.getRequestURI();
        String contextPath = req.getContextPath();
        url = url.replace(contextPath, "");

        for (MyHandlerMapping handler : this.handlerMappings) {
            try {
                Matcher matcher = handler.getPattern().matcher(url);

                if (!matcher.matches()) {
                    continue;
                }

                return handler;
            } catch (Exception e) {
                log.error(e.getMessage(), e);

                continue;
            }
        }
        return null;
    }

    @Override
    public void init(ServletConfig config) throws ServletException {
        String configPath = config.getInitParameter(CONTEXT_CONFIG_LOCATION);
        if (StringUtils.isBlank(configPath)) {
            throw new RuntimeException("未找到 configLocation 配置，启动失败");
        }

        initController(configPath);
    }

    private void initController(String configPath) {
        //01 获取文件扫描路径
        String scanPackage = getScanPackageConfig(configPath);

        //02 找到所有 有MyController 注解的类
        List<Class> controllers = new ArrayList<>();
        controllers = getControllers(scanPackage, controllers);
        this.controllers = controllers;

        //03 缓存URL映射
        this.handlerMappings = buildHandlerMappings(controllers);
    }

    private List<MyHandlerMapping> buildHandlerMappings(List<Class> controllers) {
        List<MyHandlerMapping> handlerMappings = new ArrayList<MyHandlerMapping>();

        for (Class controllerClass : controllers) {
            String baseUrl = "";
            //获取Controller的RequestMapping配置
            if (controllerClass.isAnnotationPresent(MyRequestMapping.class)) {
                MyRequestMapping requestMapping = (MyRequestMapping) controllerClass.getAnnotation(MyRequestMapping.class);
                baseUrl = requestMapping.value();
            }


            //获取Method的RequestMapping配置
            Method[] methods = controllerClass.getMethods();
            for (Method method : methods) {
                if (!method.isAnnotationPresent(MyRequestMapping.class)) {
                    continue;
                }

                MyRequestMapping requestMapping = method.getAnnotation(MyRequestMapping.class);

                String regex = ("/" + baseUrl + "/" + requestMapping.value().replaceAll("\\*", ".*")).replaceAll("/+", "/");
                Pattern pattern = Pattern.compile(regex);

                Object controller = null;
                try {
                    controller = controllerClass.newInstance();
                    handlerMappings.add(new MyHandlerMapping(pattern, controller, method));

                    log.info("Mapped " + regex + "," + method);
                } catch (InstantiationException e) {
                    e.printStackTrace();
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        }

        return handlerMappings;
    }

    private List<Class> getControllers(String scanPackage, List<Class> controllers) {
        if (StringUtils.isBlank(scanPackage)) {
            throw new RuntimeException("未找到 scanPackage 配置，启动失败");
        }

        URL url = this.getClass().getResource("/" + scanPackage.replaceAll("\\.", "/"));

        if (url == null) {
            throw new RuntimeException("scanPackage不存在或没有文件，启动失败");
        }

        File classPath = new File(url.getFile());
        for (File file : classPath.listFiles()) {
            if (file.isDirectory()) {
                controllers = getControllers(scanPackage + "." + file.getName(), controllers);
            } else {
                if (!file.getName().endsWith(".class")) {
                    continue;
                }

                String className = (scanPackage + "." + file.getName().replace(".class", ""));
                try {
                    Class<?> beanClass = Class.forName(className);

                    if (!beanClass.isAnnotationPresent(MyController.class)) {
                        continue;
                    }

                    controllers.add(beanClass);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
            }
        }

        return controllers;
    }

    private String getScanPackageConfig(String configPath) {
        Properties config = new Properties();

        //通过URL定位找到其所对应的文件，然后转换为文件流
        InputStream is = this.getClass().getClassLoader().getResourceAsStream(configPath.replace("classpath:", ""));
        try {
            config.load(is);

            String scanPackage = config.getProperty("scanPackage");
            if (StringUtils.isNoneBlank(scanPackage)) {
                return scanPackage;
            }

            throw new RuntimeException("未找到 scanPackage 配置，启动失败");
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (null != is) {
                try {
                    is.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
}
